package com.wendy.authentication;

import com.wendy.mapper.MenuMapper;
import com.wendy.mapper.ResourceMapper;
import com.wendy.model.Menu;
import com.wendy.model.Resource;
import com.wendy.utils.Constants;
import com.wendy.utils.RequestUriHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @author zhuwending
 */
@Component
@Slf4j
public class UserAccessDecisionManager implements AccessDecisionManager {

    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private ResourceMapper resourceMapper;

    /**
     * decide 方法是判定是否拥有权限的决策方法，
     * authentication 是释UserDetailsServiceImpl中循环添加到 GrantedAuthority 对象中的权限信息集合.
     * object 包含客户端发起的请求的request信息，可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
     * configAttributes 为UserInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果，此方法是为了判定用户请求的url 是否在权限表中，如果在权限表中，则返回给 decide 方法，用来判定用户是否有此权限。如果不在权限表中则放行。
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        String uri = ((FilterInvocation) o).getRequestUrl();
        uri = RequestUriHandler.handlerUri(uri);
        log.info(uri);
        if (RequestUriHandler.needPermissions(uri)) {
            return;
        }
        if (null == collection || collection.size() == 0) {
            throw new AccessDeniedException("您没有权限进行此操作");
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        if (authorities == null || authorities.size() == 0) {
            throw new AccessDeniedException("您没有权限进行此操作");
        }
        List<String> list = new ArrayList<>(16);
        collection.forEach(configAttribute -> list.add(configAttribute.getAttribute()));
        for (GrantedAuthority authority : authorities) {
            String grant = authority.getAuthority();
            if (list.contains(grant)) {
                String permissions = grant.substring(grant.indexOf("-") + 1).toLowerCase();
                Menu menu = null;
                Resource resource = null;
                if (uri.startsWith(Constants.MENU_PATH)) {
                    menu = menuMapper.selectMenuByRoleAndPath(permissions, uri);
                } else {
                    resource = resourceMapper.selectResourceByPermissionsAndPath(permissions, uri);
                }
                if (menu == null && resource == null) {
                    throw new AccessDeniedException("没有访问权限");
                } else {
                    return;
                }
            }
        }
        log.error("current operation has no permission: " + uri);
        throw new AccessDeniedException("您没有权限进行此操作");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}
